#include "GLPane.h"

#include "trackball.h"
#include <iostream>

using namespace std;


BEGIN_EVENT_TABLE(GLPane, wxGLCanvas)
    EVT_MOTION(GLPane::mouseMotion)
    EVT_LEFT_DOWN(GLPane::mouseDown)
    EVT_LEFT_UP(GLPane::mouseReleased)
    EVT_RIGHT_DOWN(GLPane::rightClick)
    EVT_LEAVE_WINDOW(GLPane::mouseLeftWindow)
    EVT_SIZE(GLPane::resized)
    EVT_KEY_DOWN(GLPane::keyPressed)
    EVT_SIZE(GLPane::onSize)
    //EVT_KEY_UP(GLPane::keyReleased)
    EVT_MOUSEWHEEL(GLPane::mouseWheelMoved)
    EVT_PAINT(GLPane::render)
END_EVENT_TABLE()


// some useful events to use


void GLPane::rightClick(wxMouseEvent& event) {}
void GLPane::mouseLeftWindow(wxMouseEvent& event) {}
//void GLPane::keyPressed(wxKeyEvent& event) {}
//void GLPane::keyReleased(wxKeyEvent& event) {}

GLPane::GLPane(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name, int* attribList) :
wxGLCanvas(parent, id, pos, size, style, name,  attribList)
{
	mesh = 0;

    spinning = moving = 0;
    zoom_fat = 1.0;
    observer_pos[0] = 0.0;
    observer_pos[1] = 0.0;
    observer_pos[2] = -10.0;

    show_grid = true;

    trackball(curquat, 0.0, 0.0, 0.0, 0.0);
    prepare3DViewport(0, 0, getWidth(), getHeight());
    Refresh();
}


void GLPane::resized(wxSizeEvent& evt)
{
    wxGLCanvas::OnSize(evt);
    prepare3DViewport(0,0, getWidth(), getHeight());
    Refresh();
}

void GLPane::setColor(float R, float G, float B)
{
	if(mesh)
	{
		mesh->setColor(R, G, B);
	}
}


void GLPane::prepare3DViewport(int topleft_x, int topleft_y, int bottomrigth_x, int bottomrigth_y)
{
    /*
     *  Inits the OpenGL viewport for drawing in 3D.
     */

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black Background
    glClearDepth(1.0f);	// Depth Buffer Setup
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glEnable(GL_DEPTH_TEST); // Enables Depth Testing
    glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_NORMALIZE);
   	glEnable(GL_COLOR_MATERIAL);
   	glEnable(GL_DEPTH_TEST);

    // Somewhere in the initialization part of your program…
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    // Create light components
    //float ambientLight[] = { 0.001f, 0.001f, 0.001f, 1.0f };
    float diffuseLight[] = { 0.8f, 0.8f, 0.8, 1.0f };
    float specularLight[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    float position[] = { 10.0, 10.0, 10.0, 0.0 };

    // Assign created components to GL_LIGHT0
    //glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
    glLightfv(GL_LIGHT0, GL_POSITION, position);


    glViewport(topleft_x, topleft_y, bottomrigth_x-topleft_x, bottomrigth_y-topleft_y);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    float ratio_w_h = (float)(bottomrigth_x-topleft_x)/(float)(bottomrigth_y-topleft_y);
    gluPerspective(45*zoom_fat /*view angle*/, ratio_w_h, 0.1 /*clip close*/, 200/*clip far*/);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}


int GLPane::getWidth()
{
    return GetSize().x;
}

int GLPane::getHeight()
{
    return GetSize().y;
}

void GLPane::showGrid(bool show){
    show_grid = show;
}

void GLPane::render( wxPaintEvent& evt )
{
    if(!IsShown()) return;

    wxGLCanvas::SetCurrent();
    wxPaintDC(this);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glTranslatef(0.0, 0.0, observer_pos[2]);

    GLfloat m[4][4];
    build_rotmatrix(m, curquat);
    glMultMatrixf(&m[0][0]);

    glTranslatef(observer_pos[0], observer_pos[1], 0.0);

    if(show_grid){ // Desenha grade
        glPushAttrib(GL_ALL_ATTRIB_BITS);
        glDisable(GL_LIGHTING);

        glBegin(GL_LINES);
        glColor3f(0.5, 0.5, 0.5);
        for(float i=-10.0; i<=10.0; i=i+1.0){
            glVertex3f(-10.0, i, 0.0);
            glVertex3f(10.0, i, 0.0);
            glVertex3f(i, -10.0, 0.0);
            glVertex3f(i, 10.0, 0.0);
        }
        glEnd();
        glPopAttrib();
    } //

    if(mesh)
    	mesh->render();

    glPopMatrix();


    // Desenho dos eixos
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    glDisable( GL_LIGHTING );
    glViewport(0, 0, 50.0, 50.0);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho (-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glMultMatrixf(&m[0][0]);

    glBegin(GL_LINES);
        glColor3f(1.0, 0.0, 0.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(1.0, 0.0, 0.0);

        glColor3f(0.0, 1.0, 0.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(0.0, 1.0, 0.0);

        glColor3f(0.0, 0.0, 1.0);
        glVertex3f(0.0, 0.0, 0.0);
        glVertex3f(0.0, 0.0, 1.0);
    glEnd();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glPopAttrib();
    //

    SwapBuffers();
}

void GLPane::mouseDown(wxMouseEvent& event)
{
    if (event.GetButton() == wxMOUSE_BTN_LEFT)
    {
        spinning = 0;
        moving = 1;
        beginx = event.GetX();
        beginy = event.GetY();
    }
}

void GLPane::mouseReleased(wxMouseEvent& event)
{
    if (event.GetButton() == wxMOUSE_BTN_LEFT)
    {
        moving = 0;
    }
}

void GLPane::mouseMotion(wxMouseEvent& event)
{
    if (moving && event.Dragging() && event.LeftIsDown())
    {
        if(!event.ShiftDown())
        {
            trackball(lastquat,
              (2.0 * beginx - getWidth()) / getWidth(),
              (getHeight() - 2.0 * beginy) / getHeight(),
              (2.0 * event.GetX() - getWidth()) / getWidth(),
              (getHeight() - 2.0 * event.GetY()) / getHeight()
            );
            beginx = event.GetX();
            beginy = event.GetY();
            spinning = 1;
            add_quats(lastquat, curquat, curquat);
            Refresh(false);
        }
        else
        {
            observer_pos[0] += (2.0 * event.GetX() - getWidth()) / getWidth();
            observer_pos[1] += (getHeight() - 2.0 * event.GetY()) / getHeight();
            //observer_pos[0] -= (beginx - event.GetX())/(float)getWidth();
            //observer_pos[1] += (beginy - event.GetY())/(float)getHeight();
            beginx = event.GetX();
            beginy = event.GetY();

            Refresh(false);
        }
    }

}

void GLPane::mouseWheelMoved(wxMouseEvent& event)
{
    if(event.ShiftDown())
    {
        if(zoom_fat > 0.01 && zoom_fat < 3.14)
        {
            zoom_fat -= (event.GetWheelRotation()/event.GetWheelDelta())*0.1;
            Refresh();
        }
        else
        {
            if((zoom_fat >= 0.1 && event.GetWheelRotation() > 0) || (zoom_fat <= 3.14 && event.GetWheelRotation() < 0))
            {
                zoom_fat -= (event.GetWheelRotation()/event.GetWheelDelta())*0.1;
                Refresh();
            }
        }

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();

        float ratio_w_h = (float)(getWidth())/(float)(getHeight());
        gluPerspective(45*zoom_fat /*view angle*/, ratio_w_h, 0.1 /*clip close*/, 200/*clip far*/);
    }
    else
    {
        observer_pos[2] += (event.GetWheelRotation()/event.GetWheelDelta());
        Refresh();
    }
}

void GLPane::onSize( wxSizeEvent& event )
{
    cout << "GLPane resizing" << endl;
    Refresh();
}


void GLPane::keyPressed(wxKeyEvent& event){
    const int inc = 100;
    static int pos = 0;

    pos += inc;
    switch(event.GetKeyCode()){
        case WXK_UP:
        {
            gluOrtho2D(pos, pos, getWidth()+pos, getHeight()+pos);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            break;
        }
        case WXK_DOWN:
        {
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();

            gluOrtho2D(inc, inc, getWidth()+inc, getHeight()+inc);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            break;
        }
        case WXK_LEFT:
        {
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();

            gluOrtho2D(inc, inc, getWidth()+inc, getHeight()+inc);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            break;
        }
        case WXK_RIGHT:
        {
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();

            gluOrtho2D(inc, inc, getWidth()+inc, getHeight()+inc);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            break;
        }

        default:
            event.Skip();
    }
}


void GLPane::setMesh(Mesh *mesh)
{
	//FIXME: Talvez deletar a mesh existente seja muito agressivo.
	if(this->mesh)
		delete this->mesh;

	this->mesh = mesh;
}

